package data

import (
	"context"
	"fmt"
	"gitee.com/shuokeyun/kratos/errors"
	"gitee.com/shuokeyun/order-service/internal/data/ent"
	"gitee.com/shuokeyun/order-service/internal/data/ent/order"
	"gitee.com/shuokeyun/order-service/internal/data/ent/orderbarcode"
	"gitee.com/shuokeyun/order-service/internal/domain"
	"strings"
	"time"
)

var _ domain.OrderRepo = (*orderRepo)(nil)

type orderRepo struct {
	client        *ent.Client
	tx            *ent.Tx
	order         *ent.OrderClient
	orderBarcode  *ent.OrderBarcodeClient
	orderExt      *ent.OrderExtClient
	orderItem     *ent.OrderItemClient
	orderOriginal *ent.OrderOriginalClient
	orderUser     *ent.OrderUserClient
}

func NewOrderRepo(client *ent.Client) domain.OrderRepo {
	return &orderRepo{
		client:        client,
		order:         client.Order,
		orderBarcode:  client.OrderBarcode,
		orderExt:      client.OrderExt,
		orderItem:     client.OrderItem,
		orderOriginal: client.OrderOriginal,
		orderUser:     client.OrderUser,
	}
}

func (o *orderRepo) Tx(ctx context.Context) (domain.OrderRepo, error) {
	tx, err := o.client.Tx(ctx)
	if err != nil {
		return nil, err
	}
	return &orderRepo{
		client:        o.client,
		tx:            tx,
		order:         tx.Order,
		orderBarcode:  tx.OrderBarcode,
		orderExt:      tx.OrderExt,
		orderItem:     tx.OrderItem,
		orderOriginal: tx.OrderOriginal,
		orderUser:     tx.OrderUser,
	}, nil
}

func (o *orderRepo) Rollback() error {
	return o.tx.Rollback()
}

func (o *orderRepo) Commit() error {
	return o.tx.Commit()
}

func (o *orderRepo) ListOrders(ctx context.Context, search *domain.SearchOrderList) ([]*domain.Order, int, error) {
	//TODO implement me
	panic("implement me")
}

func (o *orderRepo) GetOrder(ctx context.Context, id int64, fields []string) (*domain.Order, error) {
	var po *ent.Order
	var err error
	var orderFields []string
	var barcodeFields []string
	var extFields []string
	var userFields []string
	var originalFields []string
	var itemFields []string
	if len(fields) > 0 {
		for _, field := range fields {
			before, after, ok := strings.Cut(field, ".")
			if ok {
				switch before {
				case "ext":
					extFields = append(extFields, after)
				case "barcodes":
					b, a, ok := strings.Cut(after, ".")
					if ok {
						switch b {
						case "items":
							itemFields = append(itemFields, a)
						}
					} else {
						barcodeFields = append(barcodeFields, after)
					}
				case "users":
					userFields = append(userFields, after)
				case "originals":
					originalFields = append(originalFields, after)
				}
			} else {
				orderFields = append(orderFields, field)
			}
		}
		if len(orderFields) == 0 {
			orderFields = []string{"id"}
		}
		orderSelect := o.order.Query().Where(order.ID(id)).Select(orderFields...)
		if len(barcodeFields) > 0 {
			if barcodeFields[0] == "*" {
				barcodeFields = nil
				itemFields = []string{"*"}
			} else {
				barcodeFields = append(barcodeFields, "order_id")
			}
			orderSelect.WithBarcodes(func(query *ent.OrderBarcodeQuery) {
				query.Select(barcodeFields...)
				if len(itemFields) > 0 {
					if itemFields[0] == "*" {
						itemFields = nil
					} else {
						itemFields = append(itemFields, "barcode_id")
					}
					query.WithItems(func(query *ent.OrderItemQuery) {
						query.Select(itemFields...)
					})
				}
			})
		}
		if len(extFields) > 0 {
			if extFields[0] == "*" {
				extFields = nil
			} else {
				extFields = append(extFields, "order_id")
			}
			orderSelect.WithExt(func(query *ent.OrderExtQuery) {
				query.Select(extFields...)
			})
		}
		if len(originalFields) > 0 {
			if originalFields[0] == "*" {
				originalFields = nil
			} else {
				originalFields = append(originalFields, "order_id")
			}
			orderSelect.WithOriginals(func(query *ent.OrderOriginalQuery) {
				query.Select(originalFields...)
			})
		}
		if len(userFields) > 0 {
			if userFields[0] == "*" {
				userFields = nil
			} else {
				userFields = append(userFields, "order_id")
			}
			orderSelect.WithUsers(func(query *ent.OrderUserQuery) {
				query.Select(userFields...)
			})
		}
		po, err = orderSelect.Only(ctx)
	} else {
		po, err = o.order.Query().Where(order.ID(id)).WithExt().WithOriginals().WithBarcodes(func(query *ent.OrderBarcodeQuery) {
			query.WithItems()
		}).WithUsers().Only(ctx)
	}
	if err != nil {
		return nil, err
	}
	return poConvDo(po), nil
}

func (o *orderRepo) CreateOrder(ctx context.Context, do *domain.Order) (*domain.Order, error) {
	barcodes := make([]string, len(do.Barcodes))
	for k, barcode := range do.Barcodes {
		barcodes[k] = barcode.Barcode
	}
	existsBarcodes, err := o.orderBarcode.Query().
		Where(orderbarcode.Brand(do.Brand)).
		Where(orderbarcode.BarcodeIn(barcodes...)).Select("barcode").All(ctx)
	if err != nil {
		return nil, errors.ErrorDBWrap(err, "select error")
	}
	if len(existsBarcodes) > 0 {
		barcodes := make([]string, len(existsBarcodes))
		for k, barcode := range existsBarcodes {
			barcodes[k] = barcode.Barcode
		}
		return nil, errors.ErrorArgument(fmt.Sprintf("条码 %v 重复", barcodes))
	}
	err = withTx(ctx, o.client, o.tx, func(tx *ent.Tx) error {
		now := time.Now()
		create := tx.Order.Create().SetID(do.Id).SetOrderSn(do.OrderSn).
			SetAppID(do.AppId).SetStatus(do.Status).SetSettleType(do.SettleType).
			SetPriceArea(do.PriceArea).SetBrand(do.Brand).SetTotalPrice(do.TotalPrice).
			SetOrderPrice(do.OrderPrice).SetMerchantID(do.MerchantId).
			SetMerchantUserID(do.MerchantUserId).SetPayStatus(do.PayStatus).
			SetPayMethod(do.PayMethod).SetSalesmanID(do.SalesmanId).SetCreatedAt(now).SetUpdatedAt(now)
		if do.PayTime != nil {
			create.SetPayTime(*do.PayTime)
		}
		_, err := create.Save(ctx)
		if err != nil {
			return errors.ErrorDBWrap(err, "save order error")
		}
		if do.Ext != nil {
			_, err = tx.OrderExt.Create().SetID(do.Ext.Id).SetOrderID(do.Id).SetRemark(do.Ext.Remark).
				SetDepartment(do.Ext.Department).SetOutpatientNo(do.Ext.OutpatientNo).
				SetBedNo(do.Ext.BedNo).SetClinicalDiagnosis(do.Ext.ClinicalDiagnosis).
				SetCreatedAt(now).SetUpdatedAt(now).Save(ctx)
			if err != nil {
				return errors.ErrorDBWrap(err, "save order ext error")
			}
		}
		if len(do.Originals) > 0 {
			originalCreate := make([]*ent.OrderOriginalCreate, len(do.Originals))
			for k, original := range do.Originals {
				originalCreate[k] = tx.OrderOriginal.Create().SetID(original.Id).SetOrderID(do.Id).
					SetItemCode(original.ItemCode).SetItemName(original.ItemName).
					SetItemCodeDetail(original.ItemCodeDetail).SetPrice(original.Price).
					SetMarketPrice(original.MarketPrice).SetCreatedAt(now).SetUpdatedAt(now)
			}
			_, err = tx.OrderOriginal.CreateBulk(originalCreate...).Save(ctx)
			if err != nil {
				return errors.ErrorDBWrap(err, "save order original error")
			}
		}
		barcodeCreate := make([]*ent.OrderBarcodeCreate, len(do.Barcodes))
		itemCreate := make([]*ent.OrderItemCreate, 0, len(do.Barcodes))
		for k, barcode := range do.Barcodes {
			barcodeCreate[k] = tx.OrderBarcode.Create().SetID(barcode.Id).SetOrderID(do.Id).
				SetBrand(do.Brand).SetBarcode(barcode.Barcode).SetConsumable(barcode.Consumable).
				SetNumber(barcode.Number).SetStatus(barcode.Status).SetCreatedAt(now).SetUpdatedAt(now)
			if barcode.SamplingTime != nil {
				barcodeCreate[k].SetSamplingTime(*barcode.SamplingTime)
			}
			for _, item := range barcode.Items {
				itemCreate = append(itemCreate, tx.OrderItem.Create().SetID(barcode.Id).SetOrderID(do.Id).
					SetBarcodeID(barcode.Id).SetItemCode(item.ItemCode).SetItemName(item.ItemName).
					SetPrice(item.Price).SetMarketPrice(item.MarketPrice).SetCreatedAt(now).SetUpdatedAt(now))
			}
		}
		_, err = tx.OrderBarcode.CreateBulk(barcodeCreate...).Save(ctx)
		if err != nil {
			return errors.ErrorDBWrap(err, "save order barcode error")
		}
		_, err = tx.OrderItem.CreateBulk(itemCreate...).Save(ctx)
		if err != nil {
			return err
		}
		userCreate := make([]*ent.OrderUserCreate, len(do.Users))
		for k, user := range do.Users {
			userCreate[k] = tx.OrderUser.Create().SetID(user.Id).SetOrderID(do.Id).SetBarcodeID(user.BarcodeId).
				SetUserID(user.UserId).SetName(user.Name).SetAge(user.Age).SetSex(user.Sex).SetMobile(user.Mobile).
				SetCardType(user.CardType).SetCardID(user.CardId).SetCreatedAt(now).SetUpdatedAt(now)
		}
		_, err = tx.OrderUser.CreateBulk(userCreate...).Save(ctx)
		if err != nil {
			return errors.ErrorDBWrap(err, "save order user error")
		}
		return nil
	})
	return do, err
}

func (o *orderRepo) UpdateOrder(ctx context.Context, do *domain.Order, mask []string) (*domain.Order, error) {
	//TODO implement me
	panic("implement me")
}

func (o *orderRepo) DeleteOrder(ctx context.Context, id int64) error {
	//TODO implement me
	panic("implement me")
}
